package generator

import (
	"fmt"
	"github.com/kercylan98/minotaur/engine/vivid/typed/options"
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/proto"
)

const (
	GenerateModeCluster = "cluster"
	GenerateModeVivid   = "vivid"
)

var (
	clusterPackages = []protogen.GoImportPath{
		protogen.GoImportPath("github.com/kercylan98/minotaur/engine/vivid/cluster"),
		protogen.GoImportPath("github.com/kercylan98/minotaur/engine/vivid"),
	}

	vividPackages = []protogen.GoImportPath{
		protogen.GoImportPath("github.com/kercylan98/minotaur/engine/vivid"),
	}

	generatePackages = []protogen.GoImportPath{
		protogen.GoImportPath("github.com/kercylan98/minotaur/engine/vivid/typed/tm"),
		protogen.GoImportPath("github.com/kercylan98/minotaur/toolkit/log"),
		protogen.GoImportPath("time"),
	}
)

func GenerateFile(gen *protogen.Plugin, file *protogen.File, mode string) {
	if len(file.Services) == 0 && len(file.Enums) == 0 {
		return
	}

	filename := file.GeneratedFilenamePrefix + "_typed.pb.go"
	g := gen.NewGeneratedFile(filename, file.GoImportPath)

	generateHeader(gen, g, file)
	generateContent(g, file, mode)
}

func generateHeader(gen *protogen.Plugin, g *protogen.GeneratedFile, file *protogen.File) {
	g.P("// Code generated by protoc-gen-minotaur-vivid-typed. DO NOT EDIT.")
	g.P("// versions:")
	g.P("//  protoc-gen-minotaur-vivid-typed ", "1.0.0")
	protocVersion := "(unknown)"
	if v := gen.Request.GetCompilerVersion(); v != nil {
		protocVersion = fmt.Sprintf("v%v.%v.%v", v.GetMajor(), v.GetMinor(), v.GetPatch())
		if s := v.GetSuffix(); s != "" {
			protocVersion += "-" + s
		}
	}
	g.P("//  protoc           ", protocVersion)
	if file.Proto.GetOptions().GetDeprecated() {
		g.P("// ", file.Desc.Path(), " is a deprecated file.")
	} else {
		g.P("// source: ", file.Desc.Path())
	}
	g.P()
}

func generateContent(g *protogen.GeneratedFile, file *protogen.File, mode string) {
	generatePackageImports(g, file, mode)
	generateServices(g, file, mode)
}

func generatePackageImports(g *protogen.GeneratedFile, file *protogen.File, mode string) {
	g.P("package ", file.GoPackageName)
	g.P()
	g.P("var _ *options.Empty")
	g.P("var _ *time.Time")

	switch mode {
	case GenerateModeCluster:
		for _, pkg := range clusterPackages {
			g.QualifiedGoIdent(pkg.Ident(""))
		}
	case GenerateModeVivid:
		for _, pkg := range vividPackages {
			g.QualifiedGoIdent(pkg.Ident(""))
		}
	}

	for _, pkg := range generatePackages {
		g.QualifiedGoIdent(pkg.Ident(""))
	}
}

func generateServices(g *protogen.GeneratedFile, file *protogen.File, mode string) {
	if len(file.Services) == 0 {
		return
	}

	for _, service := range file.Services {

		serviceDesc := &typedServiceDesc{
			Name:     service.GoName,
			ModeName: mode,
		}

		generateServiceMethods(g, file, service, serviceDesc)

		if len(serviceDesc.Methods) > 0 {
			g.P(serviceDesc.render())
		}

	}
}

func generateServiceMethods(g *protogen.GeneratedFile, file *protogen.File, service *protogen.Service, desc *typedServiceDesc) {
	for i, method := range service.Methods {
		if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() {
			continue
		}

		methodDesc := &typedServiceMethodDesc{
			Index:   i,
			Name:    method.GoName,
			Input:   g.QualifiedGoIdent(method.Input.GoIdent),
			Output:  g.QualifiedGoIdent(method.Output.GoIdent),
			Options: &options.MethodOptions{},
		}
		if methodDesc.Input == "options.ActorRef" {
			methodDesc.Input = "vivid.ActorRef"
		}
		if methodDesc.Output == "options.ActorRef" {
			methodDesc.Output = "vivid.ActorRef"
		}

		if methodOptions, ok := proto.GetExtension(method.Desc.Options(), options.E_MethodOptions).(*options.MethodOptions); ok && methodOptions != nil {
			methodDesc.Options = methodOptions
		}

		if methodDesc.Options.Type == options.Type_FutureAsk && methodDesc.Options.Timeout == nil {
			methodDesc.Options.Timeout = proto.Uint64(1000)
		}

		desc.Methods = append(desc.Methods, methodDesc)
	}
}
